package org.luckyjourney.util;

import lombok.extern.slf4j.Slf4j;
import org.luckyjourney.constant.RedisConstant;
import org.luckyjourney.entity.Sharding;
import org.luckyjourney.entity.video.Type;
import org.luckyjourney.service.video.TypeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @Version v1.0
 * @ClassName: TypeIdGenerator
 * @description: 封装分类视频库的id生成器操作
 * @author: ygg
 */
@Component
@Slf4j
public class TypeIdGenerator {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private TypeService typeService;

    private static final Map<Long, Sharding> typeCache = new ConcurrentHashMap<>();
    private static final Map<Long, Sharding> labelCache = new ConcurrentHashMap<>();

    // 每个分片的最大存储数
    private static final Long DEFAULT_CAPACITY = 5L;


    // 需求： 提供接口自增id生成器，先修改本地缓存，再异步修改redis
    public Long getAndIncre(Long typeId){
        if(!typeCache.containsKey(typeId)) {
            return null;
        }
        Sharding sharding = typeCache.get(typeId);
        // 判断容量限制
        if(sharding.isFull()){
            // 满了，id生成器自增，更新本地缓存
            // 解耦，改造成异步
            String generatorKey = RedisConstant.TYPE_ID_GENERATOR + typeId;
            new Thread(() -> redisTemplate.opsForValue().increment(generatorKey)).start();
            sharding.setId(sharding.getId() + 1L);
            sharding.setSize(0L);
        }
        sharding.increment();
        return sharding.getId();
    }

    /**
     * 提供当前可用的id
     * @param typeId 分类id
     * @return java.lang.Long
     */
    public Long getId(Long typeId){
        Sharding sharding = typeCache.get(typeId);
        if(sharding != null){
            return sharding.getId();
        }
        return null;
    }


    // 如果在初始化之后新增了分类怎么办： 提供生成id生成器的接口
    public void addIdGenerator(Long typeId){
        if(typeId == null)
            return;
        Type type = typeService.getById(typeId);
        if(ObjectUtils.isEmpty(type))
            return;
        String idKey = RedisConstant.TYPE_ID_GENERATOR + typeId;
        if(!redisTemplate.hasKey(idKey)){
            redisTemplate.opsForValue().set(idKey, 0);
        }
    }


    /**
     *
     * @param
     * @return void
     */
    @PostConstruct
    public void initCache(){
        // 获取分类id
        List<Type> types = typeService.lambdaQuery().select(Type::getId).list();
        if(ObjectUtils.isEmpty(types)){
            return;
        }
        List<Long> typeIds = types.stream().map(Type::getId).collect(Collectors.toList());
        // 判断是否存在id生成器，不存在则初始化
        for (Long typeId : typeIds) {
            String idKey = RedisConstant.TYPE_ID_GENERATOR + typeId;
            if(!redisTemplate.hasKey(idKey)){
                redisTemplate.opsForValue().set(idKey, 0);
            }
        }
        // 获取id生成器的值 注意类型转换！！！
        List<Long> shardingIds = (List<Long>) redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            for (Long typeId : typeIds) {
                String idKey = RedisConstant.TYPE_ID_GENERATOR + typeId;
                connection.get(idKey.getBytes());
            }
            return null;
        }).stream().map(id -> Long.valueOf(id.toString())).collect(Collectors.toList());
        // 获取对应的存储数量
        List<Long> sizes = redisTemplate.executePipelined((RedisCallback<Object>) conection -> {
            for (int i = 0; i < typeIds.size(); i++) {
                String key = getTypeKey(typeIds.get(i), shardingIds.get(i));
                conection.sCard(key.getBytes());
            }
            return null;
        });
        // 封装
        for (int i = 0; i < typeIds.size(); i++) {
            typeCache.put(typeIds.get(i), new Sharding(shardingIds.get(i), sizes.get(i), DEFAULT_CAPACITY));
        }
        log.info("id生成器初始化完成");
    }

    private static String getTypeKey(Long typeId, Long shardingId) {
        return RedisConstant.SYSTEM_TYPE_STOCK + typeId + ":" + shardingId;
    }

}
